﻿using FastApi.Attributes;
using FastEmit;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace FastApi
{
    internal class FastHttp : IFastHttp
    {
        private readonly FastContext fastCtx;

        public FastHttp(FastContext fastCtx)
        {
            this.fastCtx = fastCtx;
        }

        #region Com

        public void Redirect(string url)
        {
            fastCtx.Customer = true;
            fastCtx.HttpContext.Response.StatusCode = 302;
            fastCtx.HttpContext.Response.Headers.Location = url;
        }

        public Dictionary<string, object> UserData => fastCtx.UserData;

        public void SetException(Exception ex)
        {
            fastCtx.Ex = ex;
        }

        public void SetException(string msg, int code = -1)
        {
            fastCtx.Ex = new FastException(msg, code);
        }

        public string Route => fastCtx.Route;

        public string RouteData => fastCtx.RouteData;

        public JsonSerializerOptions JsonOptions => fastCtx.JsonOptions;

        public void SetJsonOptions(JsonSerializerOptions options)
        {
            fastCtx.JsonOptions = options;
        }

        public void SetCustomer(bool ok)
        {
            fastCtx.Customer = ok;
        }

        public void SetStatusCode(int code)
        {
            if (!fastCtx.HttpContext.Response.HasStarted)
            {
                fastCtx.HttpContext.Response.StatusCode = code;
            }
        }

        public string HttpMethod => fastCtx.Action.HttpMethod;

        public bool HasForm => fastCtx.HttpContext.Request.HasFormContentType;

        #endregion

        #region Header

        public long? GetReqContentLength()
        {
            return fastCtx.HttpContext.Request.ContentLength;
        }

        public ICollection<string> GetReqHeaderKeys()
        {
            return fastCtx.HttpContext.Request.Headers.Keys;
        }

        public string GetReqHeader(string key)
        {
            return fastCtx.HttpContext.Request.Headers[key];
        }

        public void SetResHeader(string key, string value)
        {
            if (fastCtx.HttpContext.Response.Headers.ContainsKey(key))
            {
                fastCtx.HttpContext.Response.Headers[key] = value;
            }
            else
            {
                fastCtx.HttpContext.Response.Headers.Add(key, value);
            }
        }

        public void RemoveResHeader(string key)
        {
            fastCtx.HttpContext.Response.Headers.Remove(key);
        }

        public void SetResContentType(string val)
        {
            fastCtx.HttpContext.Response.ContentType = val;
        }

        public void SetResContentLength(long? length)
        {
            fastCtx.HttpContext.Response.ContentLength = length;
        }

        public void SetResFileName(string name)
        {
            fastCtx.Customer = true;
            fastCtx.HttpContext.Response.SetFileName(name);
        }

        #endregion

        #region Query

        public ICollection<string> GetQueryKeys()
        {
            return fastCtx.HttpContext.Request.Query.Keys;
        }

        public string GetQuery(string key)
        {
            return fastCtx.HttpContext.Request.Query[key];
        }

        public T GetQuery<T>(string key)
        {
            return (T)TypeConverter.Get(typeof(T), GetQuery(key));
        }

        public T QueryBind<T>()
        {
            return fastCtx.HttpContext.Request.Query.BindModel<T>();
        }

        public void QueryBindTo(object model)
        {
            fastCtx.HttpContext.Request.Query.BindToModel(model);
        }

        public string? GetQueryString()
        {
            if (fastCtx.HttpContext.Request.QueryString.HasValue)
            {
                return WebUtility.UrlDecode(fastCtx.HttpContext.Request.QueryString.Value);
            }
            return null;
        }

        #endregion

        #region Form

        public ICollection<string> GetFormKeys()
        {
            return fastCtx.Form.Keys;
        }

        public string GetForm(string key)
        {
            return fastCtx.Form[key];
        }

        public T GetForm<T>(string key)
        {
            return (T)TypeConverter.Get(typeof(T), GetForm(key));
        }

        public T FormBind<T>()
        {
            return fastCtx.Form.BindModel<T>();
        }

        public void FormBindTo(object model)
        {
            fastCtx.Form.BindToModel(model);
        }

        #endregion

        public JsonObject GetQueryAndForm()
        {
            JsonObject root = [];

            var query = fastCtx.HttpContext.Request.Query;

            //Add Query
            if (query != null && query.Count > 0)
            {
                var q = new JsonObject();
                foreach (var item in query)
                {
                    string val = item.Value;
                    q.Add(item.Key, val);
                }
                root.Add("query", q);
            }

            if (fastCtx.Form != null)
            {
                //Add Form
                if (fastCtx.Form.Count > 0)
                {
                    var form = new JsonObject();
                    foreach (var item in fastCtx.Form)
                    {
                        string val = item.Value;
                        form.Add(item.Key, val);
                    }
                    root.Add("form", form);
                }

                //Add File
                if (fastCtx.Form.Files.Count > 0)
                {
                    var file = new JsonObject();
                    foreach (var item in fastCtx.Form.Files)
                    {
                        file.Add(item.Name, new JsonObject { ["FileName"] = item.FileName, ["Length"] = item.Length });
                    }
                    root.Add("file", file);
                }

            }

            return root;
        }

        #region Cookie

        public ICollection<string> GetCookieKeys()
        {
            return fastCtx.HttpContext.Request.Cookies.Keys;
        }

        public string GetCookie(string key)
        {
            return fastCtx.HttpContext.Request.Cookies[key];
        }

        public void SetCookie(string key, string value)
        {
            fastCtx.HttpContext.Response.Cookies.Append(key, value);
        }

        public void SetCookie(string key, string value, DateTimeOffset expires)
        {
            var opt = new CookieOptions()
            {
                Expires = expires
            };
            fastCtx.HttpContext.Response.Cookies.Append(key, value, opt);
        }

        public void DeleteCookie(string key)
        {
            fastCtx.HttpContext.Response.Cookies.Delete(key);
        }

        #endregion

        #region ConnectionInfo

        public string GetIpv4()
        {
            var address = fastCtx.HttpContext.Connection.RemoteIpAddress;
            if (address != null)
            {
                return address.MapToIPv4().ToString();
            }
            return null;
        }

        public string GetIpv4ByHeader(string header = "X-Forwarded-For")
        {
            var h = fastCtx.HttpContext.Request.Headers[header];
            return h.FirstOrDefault();
        }

        public string GetIpv6()
        {
            var address = fastCtx.HttpContext.Connection.RemoteIpAddress;
            if (address != null)
            {
                return address.MapToIPv6().ToString();
            }
            return null;
        }


        public (string scheme, string host, string path, string pathBase, string queryString, bool isHttps) GetReqInfo()
        {
            var scheme = fastCtx.HttpContext.Request.Scheme;
            var host = fastCtx.HttpContext.Request.Host.HasValue ? fastCtx.HttpContext.Request.Host.Value : null;
            var path = fastCtx.HttpContext.Request.Path;
            var pathBase = fastCtx.HttpContext.Request.PathBase;
            var queryString = fastCtx.HttpContext.Request.QueryString.HasValue ? fastCtx.HttpContext.Request.QueryString.Value : null;
            var isHttps = fastCtx.HttpContext.Request.IsHttps;
            return (scheme, host, path, pathBase, queryString, isHttps);
        }

        public CancellationToken Token => fastCtx.HttpContext.RequestAborted;

        #endregion

        #region GetFile

        public IEnumerable<string> GetFileNameList()
        {
            return fastCtx.Form.Files.Select(s => s.FileName);
        }

        public string GetFileName(string name)
        {
            return fastCtx.Form.Files[name].FileName;
        }

        public int GetFileCount()
        {
            return fastCtx.Form.Files.Count;
        }

        public string GetFileName(int index)
        {
            return fastCtx.Form.Files[index].FileName;
        }

        public long GetFileLength(string name)
        {
            return fastCtx.Form.Files[name].Length;
        }

        public long GetFileLength(int index)
        {
            return fastCtx.Form.Files[index].Length;
        }

        public Stream GetFileStream(string name)
        {
            return fastCtx.Form.Files[name].OpenReadStream();
        }

        public Stream GetFileStream(int index)
        {
            return fastCtx.Form.Files[index].OpenReadStream();
        }

        public (Stream stream, string fileName, long length) GetFileStreamInfo(string name)
        {
            var file = fastCtx.Form.Files[name];
            return (file.OpenReadStream(), file.FileName, file.Length);
        }

        public (Stream stream, string fileName, long length) GetFileStreamInfo(int index)
        {
            var file = fastCtx.Form.Files[index];
            return (file.OpenReadStream(), file.FileName, file.Length);
        }

        #endregion

        #region WriteFile

        public Task WriteFileAsync(ReadOnlyMemory<byte> buffer, string fileName = null)
        {
            fastCtx.Customer = true;
            return fastCtx.HttpContext.Response.WriteFileAsync(buffer, fileName);
        }

        public Task WriteFileAsync(Stream stream, string fileName = null, long? length = null, int bufferSize = 4096)
        {
            fastCtx.Customer = true;
            return fastCtx.HttpContext.Response.WriteFileAsync(stream, fileName, length, bufferSize);
        }

        public Task WriteFileAsync(string filePath, string fileName = null, int bufferSize = 4096)
        {
            fastCtx.Customer = true;
            return fastCtx.HttpContext.Response.WriteFileAsync(filePath, fileName, bufferSize);
        }

        #endregion

        #region SaveFile

        public async Task<string> SaveFileAsync(string name, string folder, string saveName = null)
        {
            if (!Directory.Exists(folder))
            {
                Directory.CreateDirectory(folder);
            }

            var file = fastCtx.Form.Files[name];
            if (string.IsNullOrEmpty(saveName))
            {
                saveName = file.FileName;
            }
            var path = Path.Combine(folder, saveName);
            using var fs = new FileStream(path, FileMode.Create);
            await file.CopyToAsync(fs, Token);
            return path;
        }

        public async Task<string> SaveFileAsync(int index, string folder, string saveName = null)
        {
            if (!Directory.Exists(folder))
            {
                Directory.CreateDirectory(folder);
            }

            var file = fastCtx.Form.Files[index];
            if (string.IsNullOrEmpty(saveName))
            {
                saveName = file.FileName;
            }
            var path = Path.Combine(folder, saveName);
            using var fs = new FileStream(path, FileMode.Create);
            await file.CopyToAsync(fs, Token);
            return path;
        }

        public Task SaveFileAsync(string name, Stream stream)
        {
            return fastCtx.Form.Files[name].CopyToAsync(stream, Token);
        }

        public Task SaveFileAsync(int index, Stream stream)
        {
            return fastCtx.Form.Files[index].CopyToAsync(stream, Token);
        }

        #endregion

        #region Body

        public Stream GetRequestStream()
        {
            return fastCtx.HttpContext.Request.Body;
        }

        public Stream GetResponseStream()
        {
            return fastCtx.HttpContext.Response.Body;
        }

        public Task<string> GetJsonStringAsync()
        {
            if (fastCtx.Action.EnableBuffering)
            {
                fastCtx.HttpContext.Request.Body.Position = 0;
            }

            var reader = new StreamReader(fastCtx.HttpContext.Request.Body, Encoding.UTF8);
#if NET6_0
            return reader.ReadToEndAsync();
#else
            return reader.ReadToEndAsync(Token);
#endif
        }

        public ValueTask<T> GetJsonAsync<T>(JsonSerializerOptions options = null)
        {
            if (fastCtx.Action.EnableBuffering)
            {
                fastCtx.HttpContext.Request.Body.Position = 0;
            }

            if (options == null)
            {
                return JsonSerializer.DeserializeAsync<T>(fastCtx.HttpContext.Request.Body, fastCtx.JsonOptions, Token);
            }
            else
            {
                return JsonSerializer.DeserializeAsync<T>(fastCtx.HttpContext.Request.Body, options, Token);
            }
        }

        public ValueTask<object> GetJsonAsync(Type type, JsonSerializerOptions options = null)
        {
            if (fastCtx.Action.EnableBuffering)
            {
                fastCtx.HttpContext.Request.Body.Position = 0;
            }

            if (options == null)
            {
                return JsonSerializer.DeserializeAsync(fastCtx.HttpContext.Request.Body, type, fastCtx.JsonOptions, Token);
            }
            else
            {
                return JsonSerializer.DeserializeAsync(fastCtx.HttpContext.Request.Body, type, options, Token);
            }
        }

        public IAsyncEnumerable<T> GetJsonStreamAsync<T>()
        {
            if (fastCtx.Action.EnableBuffering)
            {
                fastCtx.HttpContext.Request.Body.Position = 0;
            }

            return JsonSerializer.DeserializeAsyncEnumerable<T>(fastCtx.HttpContext.Request.Body, fastCtx.JsonOptions);
        }


        #endregion

        public ValueTask WriteBytesAsync(ReadOnlyMemory<byte> buffer)
        {
            return fastCtx.HttpContext.Response.Body.WriteAsync(buffer, Token);
        }

        public async Task WriteBytesFlushAsync(ReadOnlyMemory<byte> buffer)
        {
            await fastCtx.HttpContext.Response.Body.WriteAsync(buffer, Token);
            await fastCtx.HttpContext.Response.Body.FlushAsync(Token);
        }

        public Task WriteJsonAsync<TValue>(TValue value, JsonSerializerOptions options = null)
        {
            fastCtx.Customer = true;
            if (options == null)
            {
                return fastCtx.HttpContext.Response.WriteAsJsonAsync(value, fastCtx.JsonOptions, Token);
            }
            else
            {
                return fastCtx.HttpContext.Response.WriteAsJsonAsync(value, options, Token);
            }
        }

        public Task WriteTextAsync(string text)
        {
            fastCtx.Customer = true;
            fastCtx.HttpContext.Response.ContentType = "text/plain; charset=utf-8";
            return fastCtx.HttpContext.Response.WriteAsync(text, Token);
        }

        public Task WriteTextAsync(string text, Encoding encoding)
        {
            fastCtx.Customer = true;
            return fastCtx.HttpContext.Response.WriteAsync(text, encoding, Token);
        }

        public Task FlushAsync()
        {
            return fastCtx.HttpContext.Response.Body.FlushAsync(Token);
        }

        public ValueTask DisposeAsync()
        {
            return fastCtx.HttpContext.Response.Body.DisposeAsync();
        }

        #region Service

        private IFastService _rootService;
        public IFastService RootService
        {
            get
            {
                _rootService ??= new FastService(fastCtx.WebApp, 0);
                return _rootService;
            }
        }

        private IFastService _service;
        public IFastService Service
        {
            get
            {
                if (_service == null)
                {
                    _service = new FastService(fastCtx.WebApp, 1);
                    fastCtx.HasService = true;
                }
                return _service;
            }
        }

        private IFastService _asyncService;
        public IFastService AsyncService
        {
            get
            {
                if (_asyncService == null)
                {
                    _asyncService = new FastService(fastCtx.WebApp, 2);
                    fastCtx.HasAsyncService = true;
                }
                return _asyncService;
            }
        }

        #endregion

    }
}
